/*
 * Copyright (c) 2010-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/win32image/Win32ImageBuffer.h"

#include <atlstr.h>
#include <atlimage.h>

namespace alkes {

#define IMG()   reinterpret_cast<CImage*>(this->impl_)

static const Color BGRA8888_getPixel(const uint8_t* p);
static void BGRA8888_setPixel(uint8_t* p, const Color& color);
static void BGRA8888_fillScanline(uint8_t* p, uint16_t num, const Color& color);
static const Color BGR888_getPixel(const uint8_t* p);
static void BGR888_setPixel(uint8_t* p, const Color& color);
static void BGR888_fillScanline(uint8_t* p, uint16_t num, const Color& color);

static const PixelFormatTraits* get_pixel_format_traits(PixelFormat format);
static void init_traits();


Win32ImageBuffer* Win32ImageBuffer::create(const char* filepath)
{
    CImage* image = new CImage();

    if (FAILED(image->Load(filepath)))
    {
        delete image;
        return 0;
    }
    else
    {
        return new Win32ImageBuffer(image);
    }
}

Win32ImageBuffer* Win32ImageBuffer::create(uint16_t width, uint16_t height, BitPerPixel bpp, bool validate_alpha)
{
    CImage* image = new CImage();

    if (FALSE == image->Create(width, height, bpp, validate_alpha ? CImage::createAlphaChannel : 0))
    {
        delete image;
        return 0;
    }
    else
    {
        return new Win32ImageBuffer(image);
    }
}

Win32ImageBuffer* Win32ImageBuffer::create(uint16_t width, uint16_t height, PixelFormat format, bool /*write_only*/)
{
    BitPerPixel bpp;
    bool validate_alpha = false;
    switch (format)
    {
    case AL_PIXEL_FORMAT_A8:
        bpp = AL_BPP_8;
        break;
    case AL_PIXEL_FORMAT_RGB888:
    case AL_PIXEL_FORMAT_EXT_BGR888:
        bpp = AL_BPP_24;
        break;
    case AL_PIXEL_FORMAT_RGBA8888:
    case AL_PIXEL_FORMAT_EXT_BGRA8888:
        validate_alpha = true;
    case AL_PIXEL_FORMAT_RGBX8888:
        bpp = AL_BPP_32;
        break;
    default:
        return 0;
    }

    return create(width, height, bpp, validate_alpha);
}

void Win32ImageBuffer::destroy()
{
    delete this;
}

Win32ImageBuffer::Win32ImageBuffer(void* impl)
: ImageBuffer(false)
, impl_(impl)
{
    init_traits();
}

Win32ImageBuffer::~Win32ImageBuffer()
{
    if (impl_)
    {
        delete IMG();
        impl_ = 0;
    }
}

bool Win32ImageBuffer::saveImage(const char* filepath, CompressImageFormat format)
{
    bool ret = false;
    switch (format)
    {
    case AL_COMP_IMAGE_PNG:
        ret = !FAILED(IMG()->Save(filepath, Gdiplus::ImageFormatPNG));
        break;
    case AL_COMP_IMAGE_JPEG:
        ret = !FAILED(IMG()->Save(filepath, Gdiplus::ImageFormatJPEG));
        break;
    case AL_COMP_IMAGE_BITMAP:
        ret = !FAILED(IMG()->Save(filepath, Gdiplus::ImageFormatBMP));
        break;
    default: break;
    }
    return ret;
}

void Win32ImageBuffer::getSize(uint16_t& width, uint16_t& height) const
{
    if (impl_)
    {
        width = (uint16_t)IMG()->GetWidth();
        height = (uint16_t)IMG()->GetHeight();
    }
    else
    {
        width = 0;
        height = 0;
    }
}

PixelFormat Win32ImageBuffer::getPixelFormat() const
{
    AL_PRE_COND(impl_, AL_PIXEL_FORMAT_UNKNOWN);

    // How can I get alpha component?

    int bpp = IMG()->GetBPP();
    PixelFormat format;
    switch (bpp)
    {
    case 8:
        format = AL_PIXEL_FORMAT_A8;
        break;
    case 24:
        format = AL_PIXEL_FORMAT_EXT_BGR888;
        break;
    case 32:
        format = AL_PIXEL_FORMAT_EXT_BGRA8888;
        break;

        // TODO: palette or gray scale?
    case 1:
    case 4:
    default:
        format = AL_PIXEL_FORMAT_UNKNOWN;
        break;
    }

    return format;
}

void* Win32ImageBuffer::onLockRect(const Rect& rect, uint32_t /*lock_mode*/, int32_t* pitch)
{
    AL_PRE_COND(impl_, 0);

    void* bits = 0;

    bits = IMG()->GetPixelAddress(rect.x, rect.y);

    if (bits && pitch)
        *pitch = IMG()->GetPitch();

    return bits;
}

void Win32ImageBuffer::onUnlockRect(const Rect& /*locked_rect*/)
{
    AL_PRE_COND(impl_, );
}



static const PixelFormatTraits s_ext_traits_table[] = {
    // AL_PIXEL_FORMAT_EXT_BGRA8888
    {   4, 4, 3,
        {
            { 16, 8}, { 8, 8}, {0, 8}, {24, 8}
        },
        BGRA8888_getPixel,
        BGRA8888_setPixel,
        BGRA8888_fillScanline
    },

    // AL_PIXEL_FORMAT_RGB888
    {   3, 3, -1,
        {
            { 0, 8}, { 8, 8}, {16, 8}, { 0, 0}
        },
        BGR888_getPixel,
        BGR888_setPixel,
        BGR888_fillScanline
    }
};

static GetPixelFormatTraits s_prev_func = 0;


static void init_traits()
{
    static bool s_init_traits = false;

    if (!s_init_traits)
    {
        s_prev_func = alkes_set_pixel_format_traits_func(get_pixel_format_traits);
        s_init_traits = true;
    }
}


static const PixelFormatTraits* get_pixel_format_traits(PixelFormat format)
{
    const PixelFormatTraits* traits = 0;

    if (s_prev_func && 0 != (traits = s_prev_func(format)))
        return traits;

    if (format == AL_PIXEL_FORMAT_EXT_BGRA8888)
        traits = &s_ext_traits_table[0];
    else if (format == AL_PIXEL_FORMAT_EXT_BGR888)
        traits = &s_ext_traits_table[1];

    return traits;
}


static const Color BGRA8888_getPixel(const uint8_t* p)
{
    Color c = {
        *(p+2),
        *(p+1),
        *(p  ),
        *(p+3)
    };
    return c;
}

static void BGRA8888_setPixel(uint8_t* p, const Color& color)
{
    Color c = {color.b, color.g, color.r, color.a};
    *(Color*)p = c;
}

static void BGRA8888_fillScanline(uint8_t* p, uint16_t num, const Color& color)
{
    Color c = {color.b, color.g, color.r, color.a};
    uint16_t i;
    for (i = 0; i < num; ++i, p += 4)
        *(Color*)p = c;
}

static const Color BGR888_getPixel(const uint8_t* p)
{
    Color color = {*(p+2),*(p+1),*p,0xff};
    return color;
}

static void BGR888_setPixel(uint8_t* p, const Color& color)
{
    *(p  ) = color.b;
    *(p+1) = color.g;
    *(p+2) = color.r;
}

static void BGR888_fillScanline(uint8_t* p, uint16_t num, const Color& color)
{
    uint16_t i;
    for (i = 0; i < num; ++i, p += 3)
    {
        *(p  ) = color.b;
        *(p+1) = color.g;
        *(p+2) = color.r;
    }
}

}
